/*******************************************************************************
 * Copyright (c) 2014 Francesco Gabbrielli (http://www.francescogabbrielli.it).
 * 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * 
 * Contributors:
 *     Francesco Gabbrielli - initial API and implementation
 ******************************************************************************/
package it.francescogabbrielli.android.solidalapp;

import it.francescogabbrielli.android.solidalapp.EMF;

import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiNamespace;
import com.google.api.server.spi.response.CollectionResponse;
import com.google.appengine.api.datastore.Cursor;
import com.google.appengine.datanucleus.query.JPACursorHelper;

import java.util.List;
import java.util.logging.Logger;

import javax.annotation.Nullable;
import javax.inject.Named;
import javax.persistence.EntityExistsException;
import javax.persistence.EntityNotFoundException;
import javax.persistence.EntityManager;
import javax.persistence.Query;

@Api(name = "deviceendpoint", version="v1", namespace = @ApiNamespace(ownerDomain = "francescogabbrielli.it", ownerName = "francescogabbrielli.it", packagePath = "android.solidalapp"))
public class DeviceEndpoint {
	
	protected static final Logger logger = Logger.getLogger(DeviceEndpoint.class.getName());

	/**
	 * This method lists all the entities inserted in datastore.
	 * It uses HTTP GET method and paging support.
	 *
	 * @return A CollectionResponse class containing the list of all entities
	 * persisted and a cursor to the next page.
	 */
	@SuppressWarnings({ "unchecked", "unused" })
	@ApiMethod(name = "listDevice")
	public CollectionResponse<Device> listDevice(
			@Nullable @Named("cursor") String cursorString,
			@Nullable @Named("limit") Integer limit) {

		EntityManager mgr = null;
		Cursor cursor = null;
		List<Device> execute = null;

		try {
			mgr = getEntityManager();
			Query query = mgr.createQuery("select from Device as Device");
			if (cursorString != null && cursorString != "") {
				cursor = Cursor.fromWebSafeString(cursorString);
				query.setHint(JPACursorHelper.CURSOR_HINT, cursor);
			}

			if (limit != null) {
				query.setFirstResult(0);
				query.setMaxResults(limit);
			}

			execute = (List<Device>) query.getResultList();
			cursor = JPACursorHelper.getCursor(execute);
			if (cursor != null)
				cursorString = cursor.toWebSafeString();

			// Tight loop for fetching all entities from datastore and accomodate
			// for lazy fetch.
			for (Device obj : execute)
				;
		} finally {
			mgr.close();
		}

		return CollectionResponse.<Device> builder().setItems(execute)
				.setNextPageToken(cursorString).build();
	}

	/**
	 * This method gets the entity having primary key id. It uses HTTP GET method.
	 *
	 * @param id the primary key of the java bean.
	 * @return The entity with primary key id.
	 */
	@ApiMethod(name = "getDevice")
	public Device getDevice(@Named("id") Long id) {
		EntityManager mgr = getEntityManager();
		Device device = null;
		try {
			device = mgr.find(Device.class, id);
			logger.info("getDevice "+id+": "+device);
		} finally {
			mgr.close();
		}
		return device;
	}
	
	/**
	 * This method gets the entity having registrationId
	 *
	 * @param registrationId the registration id
	 * @return the device
	 */
	@ApiMethod(name = "getDeviceByRegistration", path = "registration")	
	public Device getDeviceByRegistration(@Named("registrationId") String registrationId) {
		EntityManager mgr = getEntityManager();
		List<Device> result = null;
		try {
			Query query = mgr.createQuery(
					"select from Device as Device "
					+ "where registrationId = :regId "
					+ "order by timestamp desc");
			query.setParameter("regId", registrationId);
			query.setMaxResults(1);
			@SuppressWarnings({"unchecked"})
			List<Device> r = (List<Device>) query.getResultList();
			result = r;
		} finally {
			mgr.close();
		}
		Device ret = result!=null && !result.isEmpty() ? result.get(0) : null;
		logger.info("getDevice "+registrationId+": "+ret);
		return ret;
	}
	
	/**
	 * This inserts a new entity into App Engine datastore. If the entity already
	 * exists in the datastore, an exception is thrown.
	 * It uses HTTP POST method.
	 *
	 * @param device the entity to be inserted.
	 * @return The inserted entity.
	 */
	@ApiMethod(name = "insertDevice")
	public Device insertDevice(Device device) {
		EntityManager mgr = getEntityManager();
		try {
			if (containsDevice(device)) {
				throw new EntityExistsException("Device already exists: "+device.getRegistrationId());
			}
			mgr.persist(device);
		} finally {
			mgr.close();
		}
		return device;
	}

	/**
	 * This method is used for updating an existing entity. If the entity does not
	 * exist in the datastore, an exception is thrown.
	 * It uses HTTP PUT method.
	 *
	 * @param device the entity to be updated.
	 * @return The updated entity.
	 */
	@ApiMethod(name = "updateDevice")
	public Device updateDevice(Device device) {
		EntityManager mgr = getEntityManager();
		try {
			if (!containsDevice(device)) {
				throw new EntityNotFoundException("Device does not exist: "+device);
			}
			mgr.merge(device);
		} finally {
			mgr.close();
		}
		return device;
	}

	/**
	 * This method removes the entity with primary key id.
	 * It uses HTTP DELETE method.
	 *
	 * @param id the primary key of the entity to be deleted.
	 * @return The deleted entity.
	 */
	@ApiMethod(name = "removeDevice")
	public Device removeDevice(@Named("id") Long id) {
		EntityManager mgr = getEntityManager();
		Device device = null;
		try {
			device = mgr.find(Device.class, id);
			mgr.remove(device);
		} finally {
			mgr.close();
		}
		return device;
	}

	private boolean containsDevice(Device device) {
		if (device==null || device.getId()==null)
			return false;
		EntityManager mgr = getEntityManager();
		boolean contains = true;
		try {
			Device item = mgr.find(Device.class, device.getId());
			if (item == null) {
				contains = false;
			}
		} finally {
			mgr.close();
		}
		return contains;
	}

	private static EntityManager getEntityManager() {
		return EMF.get().createEntityManager();
	}
		
}
